/* $Id:$
 *
 * SIPproxy64 -- Proxying SIP traffic between IPv4 and IPv6 networks.
 *
 * The program is intended to support IPv4-only phones on IPv6 networks, and vice versa.
 * It was written for the 0cpm.org project in the belief that it solves problems in going
 * from an IPv4-based SIP world to a much better IPv6-based SIP world.
 *
 * This software includes RTPproxy64, a simple proxy that relays RTP traffic between IPv6
 * and IPv4.  This is designed to just do the simple protocol translation, and not become
 * a playground for functions such as wiretapping or encryption.  Given a fast processor
 * and 1 Gbps network connections on the IPv4 as well as IPv6 side, it should be feasible
 * to run 12500 SIPproxy64 sessions in parallel (although the design may have to be
 * improved to actually get that far).
 *
 * This software is provided as-is under the GNU Public License version 3.  If you need
 * a more relaxed license, please contact the copyright holder; at the very least, we will
 * listen to your requests and balance them.  Our general intention is to support freedom
 * of users to modify their software and hardware.
 *
 * From: Rick van Rein <rick@openfortress.nl>
 */


#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdarg.h>

#include <syslog.h>
#include <unistd.h>
#include <getopt.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <signal.h>

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/select.h>
#include <sys/stat.h>

#include <arpa/inet.h>
#include <netinet/in.h>

#include <0cpm/text.h>
#include <0cpm/parse.h>

#define ftext(fh,txt) fwrite ((txt).str? (txt).str: "(NULL)", sizeof (char), (txt).len, fh)

#define min(a,b) ((a)<(b)?(a):(b))

#define SIP_MESSAGE_MAX_LENGTH 4000


/* Local structures.
 */

union sockaddr_in46 {
	struct sockaddr_in  sa4;
	struct sockaddr_in6 sa6;
};

#define IDSZ 64

#if INET_ADDRSTRLEN > (INET6_ADDRSTRLEN + 2)
#  define INET46_ADDRSTRLEN INET_ADDRSTRLEN
#  define INET46_ADDRPORTSTRLEN (INET_ADDRSTRLEN + 1 + 5)
#else
#  define INET46_ADDRSTRLEN (INET6_ADDRSTRLEN + 2)
#  define INET46_ADDRPORTSTRLEN (INET6_ADDRSTRLEN + 2 + 1 + 5)
#endif

struct v4v6map {
	struct v4v6map *next;
	struct sockaddr_in  v4addr;
	struct sockaddr_in6 v6addr;
	textptr_t v4addr_str;
	textptr_t v6addr_str;
	int sox;
	unsigned short flags;
	char sox_str [INET46_ADDRPORTSTRLEN];
};

#define MAP_V4BOUND 0x01
#define MAP_V6BOUND 0x02

struct v4v6rtpmap {
	struct v4v6rtpmap *next;
	time_t timeout;
	int v4sox, v6sox;
	struct sockaddr_in  v4remote;
	struct sockaddr_in6 v6remote;
	unsigned short int v4port, v6port;
	int init_addrfam;
	char call_id [IDSZ+2];
	char from_tag [IDSZ+2];
	char to_tag [IDSZ+2];
};


/* Global variables.
 */

char *program;

struct sockaddr_in  v4local_sa, v4uplink_sa, v4recv_sa, v4send_sa;
struct sockaddr_in6 v6local_sa, v6uplink_sa, v6recv_sa, v6send_sa;

struct v4v6map *v4v6maplist = NULL;
unsigned int v4v6maplen = 0;

int seen_u = 0, seen_U = 0;

int seen_t = 0;

int signaled_HUP = 0;

int changed_rtpmap;

int contact_wrapped = 0;

//TODO: RTP port ranges would probably be nice as settable options
unsigned short int rtp_firstport = 14000, rtp_nextport = 14000, rtp_lastport = 38999;

struct v4v6rtpmap *v4v6rtpmap = NULL;

struct v4v6param {
	struct v4v6param *other;
	int addrfam;
	char *famname;
	char *sdpname;
	size_t inlen;
	size_t salen;
	struct sockaddr *local_sa;
	void *local_in;
	uint16_t *local_port;
	textptr_t local_str;
	int local_sox;
	struct sockaddr *uplink_sa;
	void *uplink_in;
	uint16_t *uplink_port;
	textptr_t uplink_str;
	struct sockaddr *recv_sa;
	void *recv_in;
	uint16_t *recv_port;
	struct sockaddr *send_sa;
	void *send_in;
	uint16_t *send_port;
	char * (*mapcat_uri_host) (char *pout, textptr_t const *uri, bool in_hdr);
	int (*map_sox) (const union sockaddr_in46 *in, textptr_t *text, union sockaddr_in46 *addr);
	char *sdp_c_first9;
};

extern struct v4v6param v6param;
extern char *mapcat_uri_host_4to6 (char *pout, textptr_t const *uri, bool in_hdr);
extern char *mapcat_uri_host_6to4 (char *pout, textptr_t const *uri, bool in_hdr);
extern int map_4to6_sox (const struct sockaddr_in  *in, textptr_t *text, struct sockaddr_in6 **addr);
extern int map_6to4_sox (const struct sockaddr_in6 *in, textptr_t *text, struct sockaddr_in  **addr);
struct v4v6param v4param = {
	&v6param,
	AF_INET ,  "IPv4", "IP4",
	sizeof (struct in_addr ), sizeof (struct sockaddr_in ),
	(struct sockaddr *) &v4local_sa,  (void *) &v4local_sa.sin_addr, &v4local_sa.sin_port, { "", 0 }, -1,
	(struct sockaddr *) &v4uplink_sa, (void *) &v4uplink_sa.sin_addr, &v4uplink_sa.sin_port,  { "", 0 },
	(struct sockaddr *) &v4recv_sa,   (void *) &v4recv_sa.sin_addr,
					  (void *) &v4recv_sa.sin_port,
	(struct sockaddr *) &v4send_sa,   (void *) &v4send_sa.sin_addr, &v4send_sa.sin_port,
	mapcat_uri_host_4to6,
	(void *) map_4to6_sox,
	"c=IN IP4 ",
};
struct v4v6param v6param = {
	&v4param,
	AF_INET6, "IPv6", "IP6",
	sizeof (struct in6_addr), sizeof (struct sockaddr_in6),
	(struct sockaddr *) &v6local_sa,  (void *) &v6local_sa.sin6_addr, &v6local_sa.sin6_port, { "", 0 }, -1,
	(struct sockaddr *) &v6uplink_sa, (void *) &v6uplink_sa.sin6_addr, &v6uplink_sa.sin6_port, { "", 0 },
	(struct sockaddr *) &v6recv_sa,   (void *) &v6recv_sa.sin6_addr,
					  (void *) &v6recv_sa.sin6_port,
	(struct sockaddr *) &v6send_sa,   (void *) &v6send_sa.sin6_addr, &v6send_sa.sin6_port,
	mapcat_uri_host_6to4,
	(void *) map_6to4_sox,
	"c=IN IP6 ",
};


static const textptr_t nulltext = { NULL, 0 };

static const textptr_t t_sip = { "sip", 3 };
static const textptr_t t_sips = { "sips", 4 };
static const textptr_t t_sip20 = { "SIP/2.0", 7 };
static const textptr_t t_sip20udp = { "SIP/2.0/UDP", 11 };
static const textptr_t t_space = { " ", 1 };
static const textptr_t t_newline = { "\r\n", 2 };
static const textptr_t t_0 = { "0", 1 };
static const textptr_t t_3pos0 = { "  0", 3 };
static const textptr_t t_70 = { "70", 2 };
static const textptr_t t_at = { "@", 1 };
static const textptr_t t_urlbra_sip = { "<sip:", 5 };
static const textptr_t t_urlket = { ">", 1 };
static const textptr_t t_colon = { ":", 1 };
static const textptr_t t_semicolon = { ";", 1 };
static const textptr_t t_equals = { "=", 1 };
static const textptr_t t_colonspace = { ": ", 2 };
static const textptr_t t_commaspace = { ", ", 2 };
static const textptr_t t_semicolon_branch_is = { ";branch=", 8 };
static const textptr_t t_semicolon_lr = { ";lr", 3 };
static const textptr_t t_semicolon_received_is = { ";received=", 10 };
static const textptr_t t_semicolon_rport_is = { ";rport=", 7 };
static const textptr_t t_invite = { "INVITE", 6 };
static const textptr_t t_subscribe = { "SUBSCRIBE", 9 };
static const textptr_t t_bye = { "BYE", 3 };
static const textptr_t t_cancel = { "CANCEL", 6 };
static const textptr_t t_ack = { "ACK", 3 };
static const textptr_t t_alertinfo = { "Alert-info", 10 };
static const textptr_t t_callinfo = { "Call-info", 9 };
static const textptr_t t_errorinfo = { "Error-info", 10 };
static const textptr_t t_inreplyto = { "In-reply-to", 11 };
static const textptr_t t_replyto = { "Reply-to", 8 };
static const textptr_t t_via = { "Via", 3 };
static const textptr_t t_from = { "From", 4 };
static const textptr_t t_to = { "To", 2 };
static const textptr_t t_callid = { "Call-id", 7 };
static const textptr_t t_contact = { "Contact", 7 };
static const textptr_t t_recordroute = { "Record-route", 12 };
static const textptr_t t_route = { "Route", 5 };
static const textptr_t t_cseq = { "Cseq", 4 };
static const textptr_t t_maxforwards = { "Max-forwards", 12 };
static const textptr_t t_useragent = { "User-agent", 10 };
static const textptr_t t_sipproxy = { "SIPproxy64 from http://devel.0cpm.org/sipproxy64/", 49 };
static const textptr_t t_sipproxy_sa4 = { "sipproxy64sa4", 13 };
static const textptr_t t_sipproxy_sa6 = { "sipproxy64sa6", 13 };
static const textptr_t t_contentlength = { "Content-length", 14 };
static const textptr_t t_contenttype = { "Content-type", 12 };
static const textptr_t t_passertedidentity = { "P-asserted-identity", 19 };
static const textptr_t t_ppreferredidentity = { "P-preferred-identity", 20 };
static const textptr_t t_path = { "Path", 4 };
static const textptr_t t_passociateduri = { "P-associated-uri", 16 };
static const textptr_t t_pcalledpartyid = { "P-called-pary-id", 16 };
static const textptr_t t_referto = { "Refer-to", 8 };
static const textptr_t t_serviceroute = { "Service-route", 13 };
static const textptr_t t_referredby = { "Referred-by", 11 };
static const textptr_t t_historyinfo = { "History-info", 12 };
static const textptr_t t_pprofilekey = { "P-profile-key", 13 };
static const textptr_t t_prefusedurilist = { "P-refused-uri-list", 18 };
static const textptr_t t_permissionmissing = { "Permission-missing", 18 };
static const textptr_t t_triggerconsent = { "Trigger-consent", 15 };
static const textptr_t t_pserveduser = { "P-served-user", 13 };
static const textptr_t t_applicationsdp = { "application/sdp", 15 };
static const textptr_t t_geolocation = { "Geolocation", 11 };

/* Commandline arguments.  See manpage for details.
 */
const char *short_opts = "htwl:L:u:U:";
struct option long_opts [] = {
	{ "v4local", 1, NULL, 'l' },
	{ "v6local", 1, NULL, 'L' },
	{ "v4uplink", 1, NULL, 'u' },
	{ "v6uplink", 1, NULL, 'U' },
	{ "wrapped", 0, NULL, 'w' },
	{ "test", 0, NULL, 't' },
	{ "help", 0, NULL, 'h' },
	{ NULL, 0, NULL,  0  },
};


/* Find a mapping from an IPv4 address to an IPv6 address.  Return NULL if not found.
 */
struct sockaddr_in6 *map_4to6 (const struct sockaddr_in *in) {
	struct v4v6map *map = v4v6maplist;
	while (map) {
		if (memcmp (&map->v4addr, in, sizeof (*in)) == 0) {
			return &map->v6addr;
		}
		map = map->next;
	}
	return NULL;
}


/* Find a socket based on an IPv4 address.  Return -1 if not found.
 */
int map_4to6_sox (const struct sockaddr_in *in, textptr_t *text, struct sockaddr_in6 **addr) {
	struct v4v6map *map = v4v6maplist;
	while (map) {
		if (memcmp (&map->v4addr, in, sizeof (*in)) == 0) {
			*text =  map->v6addr_str;
			*addr = &map->v6addr;
			return map->sox;
		}
		map = map->next;
	}
	return -1;
}


/* Find a mapping from an IPv6 address to an IPv4 address.  Return NULL if not found.
 */
struct sockaddr_in *map_6to4 (const struct sockaddr_in6 *in) {
	struct v4v6map *map = v4v6maplist;
	while (map) {
		if (memcmp (&map->v6addr, in, sizeof (*in)) == 0) {
			return &map->v4addr;
		}
		map = map->next;
	}
	return NULL;
}


/* Find a socket based on an IPv6 address.  Return -1 if not found.
 */
int map_6to4_sox (const struct sockaddr_in6 *in, textptr_t *text, struct sockaddr_in **addr) {
	struct v4v6map *map = v4v6maplist;
	while (map) {
		if (memcmp (&map->v6addr, in, sizeof (*in)) == 0) {
			*text =  map->v4addr_str;
			*addr = &map->v4addr;
			return map->sox;
		}
		map = map->next;
	}
	return -1;
}


/* A routine to map a textptr_t to an unsigned integer, for example for
 * a port number.  Return the value.  There is a default value, to be
 * returned if the textptr_t is a null text.
 */
unsigned int textptr2uint (textptr_t *src, unsigned int defaultvalue) {
	if ((!src) || textisnull (src)) {
		return defaultvalue;
	}
	unsigned int val = 0;
	int ofs = 0;
	while (ofs < src->len) {
		val *= 10;
		val += src->str [ofs] - '0';
		ofs++;
	}
	return val;
}


/* A wrapper around inet_pton() that will accept square brackets around
 * IPv6 addresses, and that will take in textptr_t values instead of
 * C-style NUL-terminated strings.
 *
 * Return 1 on success, or 0 on failure.
 */
int textptr2addr (int af, textptr_t *src, void *dst) {
	//
	// If the address is empty, clone the listening address
	// Note: It makes no sense to talk to myself, check it?
	if (src->len == 0) {
		if (af == AF_INET) {
			memcpy (dst, &v4local_sa.sin_addr,   4);
		} else {
			memcpy (dst, &v6local_sa.sin6_addr, 16);
		}
		return 1;
	}
	//
	// Strip away the optional square brackets around an IPv6 address
	textptr_t src2;
	char strbuf [ INET_ADDRSTRLEN + INET6_ADDRSTRLEN ];
	bool bracketed = (af == AF_INET6) && (src->len >= 3) && (*src->str == '[') && (src->str [src->len - 1] == ']');
	if (bracketed) {
		src2.str = src->str + 1;
		src2.len = src->len - 2;
		src = &src2;
	}
	//
	// Reject strings that are too long to be a valid address
	if (src->len + 1 > sizeof (strbuf)) {
		return 0;
	}
	//
	// Parse the address into the dst pointer
	memcpy (strbuf, src->str, src->len);
	strbuf [src->len] = '\0';
	if (inet_pton (af, strbuf, dst) == 1) {
		return 1;
	} else {
		return 0;
	}
}


/* Turn a textptr into a sockaddr, that is, an address/port combination.
 * There is a default port, used when the text parses as only an address.
 * To enforce insertion of a port, set the default port to -1.
 * Returns 1 on success, otherwise 0.
 */
int textptr2sockaddr (int af, const textptr_t *src, int defaultport, union sockaddr_in46 *dst) {
	uint16_t portmult = 1;
	uint16_t port = 0;
	textptr_t adr;
	int here = src->len - 1;
	memset (dst, 0, (af==AF_INET6) ? sizeof (struct sockaddr_in6) : sizeof (struct sockaddr_in));
	((struct sockaddr_in *) dst)->sin_family = af;
	while ((here > 0) && (src->str [here] >= '0') && (src->str [here] <= '9')) {
		port = port + (portmult * (src->str [here--] - '0'));
		portmult *= 10;
	}
	if (portmult != 1) {
		if ((here >= 0) && (src->str [here] == ':')) {
			// Strip :port from src
			adr.len = here;
		} else {
			portmult = 1;
		}
	}
	if (portmult == 1) {
		adr.len = src->len;
		port = defaultport;
	}
	adr.str = src->str;
	if (af == AF_INET) {
		((struct sockaddr_in  *) dst)->sin_port  = htons (port);
		return textptr2addr (AF_INET , &adr, &((struct sockaddr_in  *) dst)->sin_addr );
	}
	if (af == AF_INET6) {
		((struct sockaddr_in6 *) dst)->sin6_port = htons (port);
		return textptr2addr (AF_INET6, &adr, &((struct sockaddr_in6 *) dst)->sin6_addr);
	}
	return 0;
}


/* A wrapper around inet_ntop() that will produce square brackets around
 * IPv6 addresses, and that will append to a growing buffer just like
 * routines txtcat() and intcat() and so on.
 * The defaultport argument specifies what port number should remain
 * silent.  Set it to -1 if you want the port printed in any case,
 * or to 0 to never print it.
 */
char *soxcat (char *pout, int addrfam, struct sockaddr *sox, int defaultport) {
	uint16_t port;
	if (addrfam == AF_INET6) {
		*pout++ = '[';
		inet_ntop (AF_INET6, &((struct sockaddr_in6 *) sox)->sin6_addr, pout, INET6_ADDRSTRLEN);
		pout += strlen (pout);
		*pout++ = ']';
		port = ((struct sockaddr_in6 *) sox)->sin6_port;
	} else {
		inet_ntop (AF_INET , &((struct sockaddr_in  *) sox)->sin_addr , pout, INET_ADDRSTRLEN);
		pout += strlen (pout);
		port = ((struct sockaddr_in  *) sox)->sin_port;
	}
	if (defaultport) {
		port = ntohs (port);
		if (defaultport != port) {
			*pout++ = ':';
			pout = intcat (pout, port);
		}
	}
	return pout;
}

/* See if a textptr_t occurs in a list of possibilities, and return
 * the position, counting up from 1.  Return 0 if there is no match.
 * This is like an "in-set" version of texteq().
 */
int textinlist (textptr_t const *a, textptr_t const *b1, ...) {
	va_list bees;
	int idx = 0;
	va_start (bees, b1);
	while (!textisnull (b1)) {
		idx++;
		if (texteq (a, b1)) {
			va_end (bees);
			return idx;
		}
		b1 = va_arg (bees, textptr_t const *);
	}
	va_end (bees);
	return 0;
}


/* Allocate two sockets, bind them to the local address at the next RTP port.
 * Return the RTP port or, in case of problems, 0.
 */
int bind_rtp_pair (struct v4v6param *param, int *sox1, int *sox2) {
	unsigned short port_tmp;
	struct sockaddr_in  v4sa;
	struct sockaddr_in6 v6sa;
	struct sockaddr *sa;
	*sox1 = socket (param->addrfam, SOCK_DGRAM, 0);
	if (*sox1 == -1) {
		return 0;
	}
	*sox2 = socket (param->addrfam, SOCK_DGRAM, 0);
	if (*sox2 == -1) {
		close (*sox1);
		*sox1 = -1;
		return 0;
	}
	if (param->addrfam == AF_INET) {
		memcpy (&v4sa, param->local_sa, param->salen);
		// v4sa.sin_port  = htons (rtp_nextport + 1000);		//TODO:TEST:ASYMM//
		v4sa.sin_port  = htons (rtp_nextport + 0000);		//TODO:TEST:ASYMM//
		sa = (struct sockaddr *) &v4sa;
	} else {
		memcpy (&v6sa, param->local_sa, param->salen);
		v6sa.sin6_port = htons (rtp_nextport);
		sa = (struct sockaddr *) &v6sa;
	}
	if (bind (*sox1, sa, param->salen) != -1) {
		if (param->addrfam == AF_INET) {
			// v4sa.sin_port  = htons (rtp_nextport + 1001);	//TODO:TEST:ASYMM//
			v4sa.sin_port  = htons (rtp_nextport + 0001);	//TODO:TEST:ASYMM//
		} else {
			v6sa.sin6_port = htons (rtp_nextport + 1);
		}
		if (bind (*sox2, sa, param->salen) != -1) {
			return rtp_nextport;
		}
	}
	close (*sox1);
	close (*sox2);
	*sox1 = -1;
	*sox2 = -1;
	if (rtp_nextport >= rtp_lastport) {
		rtp_nextport = rtp_firstport;
	} else {
		rtp_nextport += 2;
	}
	return 0;
}


/* Update the built-in RTPproxy64, by setting up data structures for a connection
 * to establish.  The address provided may be an IPv4 or IPv6 address, for values AF_INET
 * and AF_INET6 in addrfam, respectively.  The port is in host byte order.  The To: tag is
 * only known in reply traffic, so it is optional (meaning, it may be set to NULL).
 * The return value is a nonzero string with the port number opened, or 0 on failure.
 *
 * Note that addrfam is the sender of the current message, whereas initfam is of the
 * message that initiated the interaction.  So, on requests, addrfam==initfam and on
 * responses addrfam!=initfam.
 */
unsigned short int rtpctl_update (int addrfam, int initfam,
				textptr_t *addr, textptr_t *port,
				textptr_t *call_id,
				textptr_t *from_tag,
				textptr_t *opt_to_tag) {
	struct v4v6rtpmap *map = v4v6rtpmap;
	int clidlen = min (call_id->len, IDSZ);
	int fromlen = min (from_tag->len, IDSZ);
	int optolen = (!textisnull (opt_to_tag))? min (opt_to_tag->len, IDSZ): 0;
	union sockaddr_in46 binaddr;
	memset (&binaddr, 0, sizeof (binaddr));
	binaddr.sa4.sin_family = addrfam;
	if (!textptr2addr (addrfam, addr, (addrfam==AF_INET)? (void *) &binaddr.sa4.sin_addr: (void *) &binaddr.sa6.sin6_addr)) {
		return 0;
	}
	uint16_t portnr = 0;
	int portidx = 0;
	while (portidx < port->len) {
		portnr *= 10;
		portnr += port->str [portidx] - '0';
		portidx++;
	}
	portnr = htons (portnr);
	if (addrfam == AF_INET) {
		binaddr.sa4.sin_port  = portnr;
	} else {
		binaddr.sa6.sin6_port = portnr;
	}
	//
	// Find the entry tha describes the string-described RTP connection
	int searching = 1;
	while (map) {
		//
		// Search on if the call's identity is not yet found
		if ((!strncmp (map->call_id, call_id->str, IDSZ))
				|| (!strncmp (map->from_tag, from_tag->str, IDSZ))
				|| ( !textisnull (opt_to_tag) && *map->to_tag
					&& !strncmp (map->to_tag, opt_to_tag->str, IDSZ))
				|| (map->init_addrfam != initfam)) {
			map = map->next;
		//
		// Search on if an IPv4 is supplied and it is not new and not matching
		} else if (addrfam == AF_INET  && map->v6port && memcmp (&binaddr, &map->v4remote , sizeof (map->v4remote) )) {
			map = map->next;
		//
		// Search on if an IPv6 is supplied and it is not new and not matching
		} else if (addrfam == AF_INET6 && map->v4port && memcmp (&binaddr, &map->v6remote, sizeof (map->v6remote))) {
			map = map->next;
		} else {
char ipbuf [INET46_ADDRSTRLEN + 10];
char *ipstr = ipbuf;
ipstr = txtcat (ipstr, addr);
ipstr = txtcat (ipstr, &t_space);
ipstr = txtcat (ipstr, &t_colon);
ipstr = txtcat (ipstr, &t_space);
ipstr = txtcat (ipstr, port);
*ipstr++ = 0;
syslog (LOG_DEBUG, "Remapping call from[%.*s], to[%.*s], id[%.*s].  Address is %s\n", from_tag->len, from_tag->str, textisnull (opt_to_tag)? 4: opt_to_tag->len, textisnull (opt_to_tag)? "NULL": opt_to_tag->str, call_id->len, call_id->str, ipbuf);
			break;
		}
	}
	//
	// If not found, create a new entry in the RTPmap
	if (!map) {
char ipbuf [INET46_ADDRSTRLEN + 10];
char *ipstr = ipbuf;
ipstr = txtcat (ipstr, addr);
ipstr = txtcat (ipstr, &t_space);
ipstr = txtcat (ipstr, &t_colon);
ipstr = txtcat (ipstr, &t_space);
ipstr = txtcat (ipstr, port);
*ipstr++ = 0;
syslog (LOG_DEBUG, "Mkmapping call from[%.*s], to[%.*s], id[%.*s].  Address is %s\n", from_tag->len, from_tag->str, textisnull (opt_to_tag)? 4: opt_to_tag->len, textisnull (opt_to_tag)? "NULL": opt_to_tag->str, call_id->len, call_id->str, ipbuf);
		//
		// Allocate a double map entry, keep most adminstration in the first only
		changed_rtpmap = 1;
		map = malloc (sizeof (struct v4v6rtpmap [2]));
		if (!map) {
			return 0;
		}
		memset (map, 0, sizeof (struct v4v6rtpmap [2]));
		map [0].next = &map [1];
		map [1].next = v4v6rtpmap;
		v4v6rtpmap = map;
		map [0].v4sox = map [0].v6sox = -1;
		map [1].v4sox = map [1].v6sox = -1;
		memcpy (map [0].call_id, call_id->str, clidlen);
		map [0].call_id [clidlen] = 0;
		memcpy (map [0].from_tag, from_tag->str, fromlen);
		map [0].from_tag [fromlen] = 0;
		map [0].init_addrfam = initfam;
		syslog (LOG_INFO, "New RTP map entry: %s, %s, %s, %.*s\n", map [0].call_id, map [0].from_tag, (*map [0].to_tag)? map [0].to_tag: "*NULL*", port->len, port->str);
	}
	if (opt_to_tag && !*map->to_tag) {
		memcpy (map [0].to_tag, opt_to_tag->str, optolen);
		map [0].to_tag [optolen] = 0;
	}
	//
	// Update the RTPmap expiration timer
	map->timeout = time (NULL) + 60;
	//
	// Update the remote socket address for this RTPmap
	if (addrfam == AF_INET) {
		if (! map->v4remote.sin_family ) {
			int i;
			uint16_t portnr;
			changed_rtpmap = 1;
			map->v4remote.sin_family  = addrfam;
			portnr = 0;
			for (i=0; i<port->len; i++) {
				portnr = portnr * 10 + port->str [i] - '0';
			}
			map->v4remote.sin_port = htons (portnr);
			if (!textptr2addr (addrfam, addr, &map->v4remote.sin_addr )) {
				syslog (LOG_ERR, "Error parsing remote IPv4 address %.*s for RTP\n", addr->len, addr->str);
			} else {
				syslog (LOG_DEBUG, "Correctly parsed remote IPv4 address %.*s for RTP\n", addr->len, addr->str);
			}
		}
	} else {
		if (! map->v6remote.sin6_family) {
			int i;
			uint16_t portnr;
			changed_rtpmap = 1;
			map->v6remote.sin6_family = addrfam;
			portnr = 0;
			for (i=0; i<port->len; i++) {
				portnr = portnr * 10 + port->str [i] - '0';
			}
			map->v6remote.sin6_port = htons (portnr);
			if (!textptr2addr (addrfam, addr, &map->v6remote.sin6_addr)) {
				syslog (LOG_ERR, "Error parsing remote IPv6 address %.*s for RTP\n", addr->len, addr->str);
			} else {
				syslog (LOG_DEBUG, "Correctly parsed remote IPv6 address %.*s for RTP\n", addr->len, addr->str);
			}
		}
	}
	//
	// Update the local socket addresses for this RTPmap (toggle from in to forward address family)
	if (addrfam != AF_INET) {
		while (!map->v4port) {
			changed_rtpmap = 1;
			map->v4port = bind_rtp_pair (&v4param, &map [0].v4sox, &map [1].v4sox);
		}
		return map->v4port;
	} else {
		while (!map->v6port) {
			changed_rtpmap = 1;
			map->v6port = bind_rtp_pair (&v6param, &map [0].v6sox, &map [1].v6sox);
		}
		return map->v6port;
	}
}


/* Delete a media stream from the RTPproxy64 over its command channel.  The connection is
 * identified in the same way as when updating the RTPproxy64, although the from_tag and
 * to_tag may now be reversed by the initiator of a BYE request.  This means you should
 * invoke this function a second time with exchanged from_tag and to_tag.  This function
 * supports optional tags for both arguments in support of that.  The SIP traffic usually
 * takes care of duplicting that information, but this function will not fail if you tried
 * to delete a non-existing connection.
 */
void rtpctl_delete (textptr_t *call_id, textptr_t *opt_from_tag, textptr_t *opt_to_tag) {
	struct v4v6rtpmap *map = v4v6rtpmap;
	//
	// Find the entry tha describes the string-described RTP connection
	while (map) {
		if (strncmp (map->call_id, call_id->str, IDSZ)
				|| ( (!textisnull (opt_from_tag)) && strncmp (map->from_tag, opt_from_tag->str, IDSZ))
				|| ( (!textisnull (opt_to_tag) && *map->to_tag
					&& strncmp (map->to_tag, opt_to_tag->str, IDSZ)))) {
			map = map->next;
		} else {
			//
			// When found, set the entry to expire immediately
			map->timeout = time (NULL);
			return;
		}
	}
}

/* Insert an informational reply or an error, and send it to the sender of a request,
 * using the provided code and description.
 */
void sip_reply (textptr_t *request, unsigned short code, char *descr, struct v4v6map *recvmap, struct v4v6param *in) {
	textptr_t method;
	textptr_t requri;
	textnullify (&method);
	textnullify (&requri);
	if (!sip_splitline_request (request, &method, &requri)) {
		return; // Avoid infinite loops of responses
	}
	if (texteq (&method, &t_ack)) {
		syslog (LOG_DEBUG, "Supressed response to ACK: %03d %s", code, descr);
		return; // Never respond to ACK
	}
	char replybuf [SIP_MESSAGE_MAX_LENGTH + 100]; //TODO// Get this out of here -- and think about memoverlay
	char *here = replybuf;
	here += sprintf (here, "SIP/2.0 %03d %s\r\n", code, descr);
	textptr_t h_nm, h_val;
	//
	// Pass through headers in forward, clone From: To: Call-id: Contact: Cseq:adapted
	if (sip_firstheader (request, &h_nm, &h_val)) do {
		int cseq_skipped = 0;
		// Modify header treatment for Cseq:
		if (texteq (&h_nm, &t_cseq)) {
			while ((h_val.len > cseq_skipped) && (h_val.str [h_val.len - 1 - cseq_skipped] != ' ')) {
				// Remove end characters with the method name
				cseq_skipped++;
			}
			h_val.len -= cseq_skipped;
		} else if (!(texteq (&h_nm, &t_from) || texteq (&h_nm, &t_to) || texteq (&h_nm, &t_contact) || texteq (&h_nm, &t_callid))) {
			continue;
		}
		// Now reproduce the header, possibly modified:
		here = txtcat (here, &h_nm);
		here = txtcat (here, &t_colonspace);
		here = txtcat (here, &h_val);
		if (cseq_skipped) {
			here = txtcat (here, &method);	// From start-line, not from Cseq:
			h_val.len += cseq_skipped;	// Don't disturb sip_nextheader()
		}
		here = txtcat (here, &t_newline);
	} while (sip_nextheader (request, &h_nm, &h_val));
	//
	// Pass through headers in reverse, clone Via:
	if (sip_lastheader (request, &h_nm, &h_val)) do {
		if (texteq (&h_nm, &t_via)) {
			here = txtcat (here, &h_nm);
			here = txtcat (here, &t_colonspace);
			here = txtcat (here, &h_val);
			here = txtcat (here, &t_newline);
		}
	} while (sip_prevheader (request, &h_nm, &h_val));
	// Append a Max-forwards: header
	here = txtcat (here, &t_maxforwards);
	here = txtcat (here, &t_colonspace);
	here = txtcat (here, &t_70);
	here = txtcat (here, &t_newline);
	// Append a User-agent: header for SIPproxy64, then an empty line, not attachment
	here = txtcat (here, &t_useragent);
	here = txtcat (here, &t_colonspace);
	here = txtcat (here, &t_sipproxy);
	here = txtcat (here, &t_newline);
	// Append a Content-length: 0 header
	here = txtcat (here, &t_contentlength);
	here = txtcat (here, &t_colonspace);
	here = txtcat (here, &t_0);
	here = txtcat (here, &t_newline);
	// Finish the headers with an empty line (and attach no body)
	here = txtcat (here, &t_newline);
	uint16_t replylen = ((intptr_t) here) - ((intptr_t) replybuf);
	if (in) {
		if (seen_t) {
			char reply_str [INET46_ADDRPORTSTRLEN];
			*soxcat (reply_str, in->addrfam, in->recv_sa, 5060) = '\0';
			printf ("Replying over %s to %s from socket %d (%d %s)\n", in->famname, reply_str, in->local_sox, code, descr);
			fwrite (replybuf, replylen, 1, stdout);	//DEBUG
			printf ("\n");
			if (--seen_t) {
				return;
			} else {
				exit (0);
			}
		}
		ssize_t sent = sendto (recvmap->sox, replybuf, replylen, MSG_EOR, in->recv_sa, in->salen);
		if (sent < replylen) {
			syslog (LOG_ERR, "Sent only %zd out of %d bytes\n", sent, replylen);
		}
	} else {
		syslog (LOG_ERR, "Failed to reply in lieu of address to send to\n");
	}
}


/* Prepend a new mapping to the list of IPv4/IPv6 bidirectional mappings.
 * Search structures are not optimised, because they will not usually cover long lists.
 * If changes are needed, contact the author -- he may have overlooked your situation.
 * Patches incorporating uthash.h and tested at scale are probably not a bad idea.
 *
 * It is possible to repeat remote addresses, such as a SIP Trunk taking various
 * numbers, and various phones, that we locally consider separate.  This means that
 * any mapping that we found must not be bound to the address that we find for it.
 */
void insert_mapping (textptr_t *v4addr_str, struct sockaddr_in  *v4addr,
		     textptr_t *v6addr_str, struct sockaddr_in6 *v6addr) {
	/* See if a mapping with the same bound address already exists */
	struct v4v6map *map = v4v6maplist;
	while (map) {
		if (map->flags && MAP_V4BOUND) {
			if (memcmp (&map->v4addr, v4addr, sizeof (*v4addr)) == 0) {
				char v4ip [INET46_ADDRPORTSTRLEN];
				*soxcat (v4ip, AF_INET,  (struct sockaddr *) v4addr, -1) = '\0';
				fprintf (stderr, "%s: Local %s cannot be bound for multiple remote address:port pairs\n", program, v4ip);
				exit (1);
			}
		}
		if (map->flags && MAP_V6BOUND) {
			if (memcmp (&map->v6addr, v6addr, sizeof (*v6addr)) == 0) {
				char v6ip [INET46_ADDRPORTSTRLEN];
				*soxcat (v6ip, AF_INET6,  (struct sockaddr *) v6addr, -1) = '\0';
				fprintf (stderr, "%s: Local %s cannot be bound for multiple remote address:port pairs\n", program, v6ip);
				exit (1);
			}
		}
		map = map->next;
	}
	map = malloc (sizeof (struct v4v6map));
	if (!map) {
		syslog (LOG_CRIT, "Out of memory\n");
		exit (1);
	}
	memcpy (&map->v4addr, v4addr, sizeof (*v4addr));
	memcpy (&map->v6addr, v6addr, sizeof (*v6addr));
	memcpy (&map->v4addr_str, v4addr_str, sizeof (textptr_t));
	memcpy (&map->v6addr_str, v6addr_str, sizeof (textptr_t));
	map->sox = -1;
	map->flags = 0;
	map->next = v4v6maplist;
	v4v6maplist = map;
	v4v6maplen++;
}


/* Lookup the named URI parameter.  Return true if it is found, with
 * its name and value pointers filled in the parameters given.
 * Store a pointer to the first parameter in *param0, or the end
 * of the uri if none is found.
 */
bool mapcat_find_uriparam (textptr_t const *sought, textptr_t const *uri,
			textptr_t *nm, textptr_t *val) {
	if (sip_firstparm_inuri (uri, nm, val)) do {
		if (texteq (sought, nm)) {
			return true;
		}
	} while (sip_nextparm_inuri (uri, nm, val));
	return false;
}


/* Append URI parameters, possibly dropping one as supplied and
 * attaching the extra if it is not nullified.
 * Return the updated output pointer.
 */
char *mapcat_output_uriparam (char *pout, textptr_t const *uri, textptr_t const *drop) {
	textptr_t pnm, pval;
	if (sip_firstparm_inuri (uri, &pnm, &pval)) do {
		if (!texteq (&pnm, drop)) {
			//
			// Output ";pnm"
			pout = txtcat (pout, &t_semicolon);
			pout = txtcat (pout, &pnm);
			//
			// Optionally output "=pval"
			if (pval.str [-1] == '=') {
				pout = txtcat (pout, &t_equals);
				pout = txtcat (pout, &pval);
			}
		}
	} while (sip_nextparm_inuri (uri, &pnm, &pval));
	return pout;
}


/* Map a URI from IPv4 to IPv6 if possible, and concatenate it to the
 * output pointer.  Fallback to concatenating the input URI on failure.
 * Return the updated output pointer.
 */
char *mapcat_uri_host_4to6 (char *pout, textptr_t const *uri, bool in_hdr) {
	//
	// Analyse the URI structure
	textptr_t proto;
	textptr_t user;
	textptr_t pass;
	textptr_t dom;
	uint16_t port;
	bool ok = true;
	ok = ok && sip_components_inuri (uri, &proto, &user, &pass, &dom, &port);
	ok = ok && textinlist (&proto, &t_sip, &t_sips, &nulltext);
	if (!ok) {
		// Bail out with the literal URI copy
		return txtcat (pout, uri);
	}
	//
	// Test if the URI holds an overriding URI parameter
	textptr_t pname;
	textptr_t pval;
	bool newparam = false;
	struct sockaddr_in6 *v6sa = NULL;
	if (!mapcat_find_uriparam (&t_sipproxy_sa6, uri, &pname, &pval)) {
		//
		// Not found, place pname at the end and empty pval
		pname.str = &uri->str [uri->len];
		pname.len = 0;
		//
		// Look for a mapping based on URI components
		struct sockaddr_in v4sa;
		memset (&v4sa, 0, sizeof (v4sa));
		v4sa.sin_family = AF_INET;
		v4sa.sin_port = htons (port);
		if (textptr2addr (AF_INET, &dom, &v4sa.sin_addr) != 1) {
			syslog (LOG_DEBUG, "mapcat_uri_host_4to6 -> Failed to parse %.*s as IPv4 address\n", dom.len, dom.str);
			return txtcat (pout, uri);
		}
		//
		// Try to map the IPv4 address to an IPv6 address
		v6sa = map_4to6 (&v4sa);
		if (in_hdr && !v6sa) {
			v6sa = &v6local_sa;
			newparam = true;
		}
	}
	//
	// Write out the URI, but with the mapped host
	//TODO// syslog (LOG_DEBUG, "mapcat_uri_host_4to6 -> Succeeded to map %.*s to %.*s\n", dom.len, dom.str, v6ip.len, v6ip.str);
	pout = txtcat (pout, &proto);
	pout = txtcat (pout, &t_colon);
	if (!textisnull (&user)) {
		pout = txtcat (pout, &user);
		if (!textisnull (&pass)) {
			pout = txtcat (pout, &t_colon);
			pout = txtcat (pout, &pass);
		}
		pout = txtcat (pout, &t_at);
	}
	//
	// Let the new address depend on the code above
	if (v6sa) {
		pout = soxcat (pout, AF_INET6, (struct sockaddr *) v6sa, 5060);
	} else if (in_hdr) {
		pout = txtcat (pout, &pval);
	} else {
		pout = txtcat (pout, &dom);
		if (port > 0) {
			pout = txtcat (pout, &t_colon);
			pout = intcat (pout, port);
		}
	}
	//
	// Append parameters, possibly excluding the overriding one (if any)
	pout = mapcat_output_uriparam (pout, uri, &pname);
	if (newparam) {
		pout = txtcat (pout, &t_semicolon);
		pout = txtcat (pout, &t_sipproxy_sa4);
		pout = txtcat (pout, &t_equals);
		int portofs = dom.len;
		if (dom.str [portofs] == ':') {
			do {
				portofs++;
				if (dom.str + portofs > uri->str + uri->len) {
					portofs = 0;
					break;
				}
			} while ((dom.str [portofs] >= '0') && (dom.str [portofs] <= '9'));
		}
		dom.len = portofs;
		pout = txtcat (pout, &dom);
	}
	//
	// Return the updated output pointer
	return pout;
}


/* Map a URI from IPv6 to IPv4 if possible, and concatenate it to the
 * output pointer.  Fallback to concatenating the input URI on failure.
 * Return the updated output pointer.
 */
char *mapcat_uri_host_6to4 (char *pout, textptr_t const *uri, bool in_hdr) {
	//
	// Analyse the URI structure
	textptr_t proto;
	textptr_t user;
	textptr_t pass;
	textptr_t dom;
	uint16_t port;
	bool ok = true;
	ok = ok && sip_components_inuri (uri, &proto, &user, &pass, &dom, &port);
	ok = ok && textinlist (&proto, &t_sip, &t_sips, &nulltext);
	if (!ok) {
		// Bail out with the literal URI copy
		return txtcat (pout, uri);
	}
	//
	// Test if the URI holds an overriding URI parameter
	textptr_t pname;
	textptr_t pval;
	bool newparam = false;
	struct sockaddr_in *v4sa = NULL;
	if (!mapcat_find_uriparam (&t_sipproxy_sa4, uri, &pname, &pval)) {
		//
		// Not found, place pname at the end and empty pval
		pname.str = &uri->str [uri->len];
		pname.len = 0;
		//
		// Look for a mapping based on URI components
		struct sockaddr_in6 v6sa;
		memset (&v6sa, 0, sizeof (v6sa));
		v6sa.sin6_family = AF_INET6;
		v6sa.sin6_port = htons (port);
		if (textptr2addr (AF_INET6, &dom, &v6sa.sin6_addr) != 1) {
			syslog (LOG_DEBUG, "mapcat_uri_host_6to4 -> Failed to parse %.*s as IPv6 address\n", dom.len, dom.str);
			return txtcat (pout, uri);
		}
		//
		// Try to map the IPv6 address to an IPv4 address
		v4sa = map_6to4 (&v6sa);
		if (in_hdr && !v4sa) {
			v4sa = &v4local_sa;
			newparam = true;
		}
	}
	//
	// Write out the URI, but with the mapped host
	//TODO// syslog (LOG_DEBUG, "mapcat_uri_host_6to4 -> Succeeded to map %.*s to %.*s\n", dom.len, dom.str, v4ip.len, v4ip.str);
	pout = txtcat (pout, &proto);
	pout = txtcat (pout, &t_colon);
	if (!textisnull (&user)) {
		pout = txtcat (pout, &user);
		if (!textisnull (&pass)) {
			pout = txtcat (pout, &t_colon);
			pout = txtcat (pout, &pass);
		}
		pout = txtcat (pout, &t_at);
	}
	//
	// Let the new address depend on the code above
	if (v4sa) {
		pout = soxcat (pout, AF_INET, (struct sockaddr *) v4sa, 5060);
	} else if (in_hdr) {
		pout = txtcat (pout, &pval);
	} else {
		pout = txtcat (pout, &dom);
		if (port > 0) {
			pout = txtcat (pout, &t_colon);
			pout = intcat (pout, port);
		}
	}
	//
	// Append parameters, possibly excluding the overriding one (if any)
	pout = mapcat_output_uriparam (pout, uri, &pname);
	if (newparam) {
		pout = txtcat (pout, &t_semicolon);
		pout = txtcat (pout, &t_sipproxy_sa6);
		pout = txtcat (pout, &t_equals);
		int portofs = dom.len;
		if (dom.str [portofs] == ':') {
			do {
				portofs++;
				if (dom.str + portofs > uri->str + uri->len) {
					portofs = 0;
					break;
				}
			} while ((dom.str [portofs] >= '0') && (dom.str [portofs] <= '9'));
		}
		dom.len = portofs;
		pout = txtcat (pout, &dom);
	}
	//
	// Return the updated output pointer
	return pout;
}


/* Given a header, retrieve all parameters with given names,
 * and register their values.  Values are assumed to be null strings
 * on calling (which would normally be needed for alternate paths
 * of execution in the calling function).  The array param_nm is
 * assumed to be a list of names, ending in a null string.  The
 * array param_val is assumed to contain as many value pointers
 * as there are non-nulltext strings in param_nm.
 * TODO: Upper/lowercase, does it matter to SIP?  It matters to this code!
 */
void find_parameters_inheader (textptr_t const *header, textptr_t const param_nm [], textptr_t *param_val []) {
	textptr_t h_nm, h_val;
	if (sip_firstparm_inheader (header, &h_nm, &h_val)) do {
		for (int idx = 0; !textisnull (&param_nm [idx]); idx++) {
			if (textisnull (param_val [idx]) && texteq (&param_nm [idx], &h_nm)) {
				/* Found name.  Copy value, then proceed to next header. */
				memcpy (param_val [idx], &h_val, sizeof (textptr_t));
				break;
			}
		}
	} while (sip_nextparm_inheader (header, &h_nm, &h_val));
}


/* Try to retrieve a new line from an SDP body.  Return the result
 * in line, possibly setting it to the null string.  If the null
 * string is returned in line, then the return value is 0 to
 * indicate failure, otherwise it is 1 to indicate success.
 * Note that the format of the lines is also somewhat tested;
 * it should end in a sequence of CR and/or LF, and the contents
 * should be X=... or otherwise no new line is reported.
 * Note that the body reference is updated to point to the next
 * line.
 */
int sdp_fetch_line (textptr_t *body, textptr_t *line) {
	if (textisnull (body)) {
		textnullify (line);
		return 0;
	}
	line->str = body->str;
	line->len = 0;
	while ((body->len > line->len) && (*body->str != '\r') && (*body->str != '\n')) {
		line->len++;
		body->str++;
	}
	int precrlen = line->len;
	while ((body->len > line->len) && ((*body->str == '\r') || (*body->str == '\n'))) {
		line->len++;
		body->str++;
	}
	if ((line->len < 3) || (line->len == precrlen) ||
			(line->str [0] < 'a') || (line->str [0] > 'z') ||
			(line->str [1] != '=')) {
		body->str = line->str;	// Reset body
		textnullify (line);
		return 0;
	}
	// Success.  Update body
	body->len -= line->len;
	return 1;
}


/* Try to find a c= line in the SDP body, but stop when an m= line
 * is encountered.  This can both be used to find the session_c at
 * the beginning of the SDP content, or the media_c following an
 * m= line.  If failed, the c_output will be set to the null text.
 */
void sdp_chase_c_until_m (textptr_t *body, textptr_t *c_output) {
	textptr_t chase;
	memcpy (&chase, body, sizeof (textptr_t));
	textptr_t line;
	while (sdp_fetch_line (&chase, &line)) {
		if (line.str [0] == 'm') {
			textnullify (c_output);
			return;
		}
		if (line.str [0] == 'c') {
			memcpy (c_output, &line, sizeof (textptr_t));
			return;
		}
	}
	textnullify (c_output);
}


/* Handle an incoming SIP message.  Read it from the socket of the given map,
 * perform sanity checks, rewrite addresses, add a Via header and pass it on.
 * On the fly, if need be, initiate or terminate media forwarding through the
 * built-in RTPproxy64.  Also, where applicable, send notifications back to
 * the originator.
 */
void handle_sip (struct v4v6map *recvmap) {
	//
	// ### DECIDE ON PROTOCOL FAMILIES TO APPLY ###
	//
	struct v4v6param *in, *fwd;
	if (recvmap->flags & MAP_V4BOUND) {
		in  = &v4param;
		fwd = &v6param;
	} else {
		in  = &v6param;
		fwd = &v4param;
	}

	//
	// ### RECEIVE THE SIP MESSAGE ###
	//
	char sipbuf [SIP_MESSAGE_MAX_LENGTH + 2];
	struct iovec iov = { sipbuf, sizeof (sipbuf) };
	struct sockaddr_in6 addr6;
	struct sockaddr_in  addr4;
	struct msghdr mh = { &addr6, sizeof (addr6), &iov, 1, NULL, 0, 0 };
	mh.msg_name = in->recv_sa;
	mh.msg_namelen = in->salen;
	textptr_t sip;
	sip.str = sipbuf;
	sip.len = recvmsg (recvmap->sox, &mh, MSG_DONTWAIT);
	char recv_str [INET46_ADDRPORTSTRLEN];
	*soxcat (recv_str, in->addrfam, in->recv_sa, 5060) = '\0';
	if (sip.len == -1) {
		syslog (LOG_ERR, "%s: Failure while receiving SIP message: %s\n", program, strerror (errno));
		return;
	}
	if (sip.len < 50) {
		// Probably a SIP PING.  TODO: Is this a hack or a real thing?
		syslog (LOG_ERR, "Unbelievable length for SIP message: %d\n", sip.len);
		return;
	}
	if (sip.len > SIP_MESSAGE_MAX_LENGTH) {
		sip_reply (&sip, 513, "Message Too Large", recvmap, in);
		syslog (LOG_ERR, "SIP message overzealously sized: %d\n", sip.len);
		return;
	}
	if (seen_t) {
		printf ("Received SIP message over %s from %s to socket %d\n", in->famname, recv_str, recvmap->sox);
		ftext (stdout, sip);
	}

	//
	// ### PARSE IT AS EITHER A RESPONSE OR A REQUEST ###
	//
	textptr_t attachment;
	sip_normalise (&sip, &attachment);
	int err = 0;
	textptr_t respcode;
	textptr_t respdescr;
	textptr_t method;
	textptr_t requri;
	bool resp;
	textnullify (&respcode);
	textnullify (&respdescr);
	textnullify (&method);
	textnullify (&requri);
	resp = (sip.len > 10) && (memcmp (sip.str, "SIP/2.0 ", 8) == 0);
	if (resp) {
		err = err || (sip.len < 12);
		err = err || (sip.str [ 8] < '0') || (sip.str [ 8] > '9');
		err = err || (sip.str [ 9] < '0') || (sip.str [ 9] > '9');
		err = err || (sip.str [10] < '0') || (sip.str [10] > '9');
		err = err || (sip.str [11] != ' ');
		err = err || !sip_splitline_response (&sip, &respcode, &respdescr);
	} else {
		err = err || !sip_splitline_request (&sip, &method, &requri);
	}
	if (err) {
		syslog (LOG_CRIT, "Fatal: Syntax error in SIP start-line\n");
		return;
	}

	//
	// ### GATHER THE VALUES OF CORE HEADERS ###
	//
	textptr_t h_nm, h_val;
	textptr_t *here;
	int idx;
#define NUM_CORE_HDR 8
	textptr_t from, to, via0, via1, route0, route1, conttp, callid;
	textptr_t *corehdr_val [NUM_CORE_HDR] = { &from, &to, &via0, &via1, &route0, &route1, &conttp, &callid };
	static const textptr_t corehdr_nm [NUM_CORE_HDR] = { { "From", 4 }, { "To", 2 }, { "Via", 3 }, { "Via", 3 }, { "Route", 5 }, { "Route", 5 }, { "Content-type", 12 }, { "Call-id", 7 } };
	for (idx = 0; idx < NUM_CORE_HDR; idx++) {
		textnullify (corehdr_val [idx]);
	}
	if (sip_firstheader (&sip, &h_nm, &h_val)) do {
		for (idx = 0; idx < NUM_CORE_HDR; idx++) {
			if (textisnull (corehdr_val [idx]) && texteq (&corehdr_nm [idx], &h_nm)) {
				memcpy (corehdr_val [idx], &h_val, sizeof (h_val));
				break;
			}
		}
	} while (sip_nextheader (&sip, &h_nm, &h_val));
#if 0
printf ("HEADERS FOUND:\n\n");
for (idx = 0; idx < NUM_CORE_HDR; idx++) {
if (!textisnull (corehdr_val [idx])) {
ftext (stdout, corehdr_nm [idx]);
printf (": ");
ftext (stdout, *corehdr_val [idx]);
printf ("\n");
}
}
printf ("\n");
#endif
	//
	// Find the 2nd route URIs in the Route: headers (and make shorter versions)
	textptr_t route_uri1;
	textptr_t *routeN = NULL;
	if (!textisnull (&route0)) {
		//
		// There was a 1st Route: header -- it must contain the 1st URI
		if (!sip_firsturi_inheader (&route0, &route_uri1)) {
			sip_reply (&sip, 400, "Bad Request (Route: without URI)", recvmap, in);
			return;
		}
		// Try to find a 2nd URI in the 1st Route: header
		if (sip_nexturi_inheader (&route0, &route_uri1)) {
			//
			// Additional URI in same Route: header
			routeN = &route0;
		} else {
			//
			// Empty the spent route0, but keep its pointer
			route0.str += route0.len;
			route0.len  = 0;
			//
			// Try to find a 2nd URI in an optional 2nd Route: header
			if (!textisnull (&route1)) {
				//
				// There was a 2nd Route: header -- it must contain a URI
				if (!sip_firsturi_inheader (&route1, &route_uri1)) {
					sip_reply (&sip, 400, "Bad Request (Route: without URI)", recvmap, in);
					return;
				}
				routeN = &route1;
			}
		}
	}
	//
	// Update *routeN beyond the route_uri1 in it (maybe nullify *routeN)
	if (routeN != NULL) {
		char *comma = memchr (route_uri1.str + route_uri1.len, ',',
				((intptr_t) routeN->str - (intptr_t) route_uri1.str) +
				routeN->len - route_uri1.len);
		if (comma == NULL) {
			//
			// Empty the spent routeN, but keep its pointer
			routeN->str += routeN->len;
			routeN->len  = 0;
		} else {
			//
			// Due to the comma, there must be an extra URI in routeN
			routeN->len -= ((intptr_t) comma + 1 - (intptr_t) routeN->str);
			routeN->str  = comma + 1;
		}
	}

	//
	// ### DECIDE ON ROUTING ###
	//
	textptr_t fwd_sender_str;
	textnullify (&fwd_sender_str);
	struct sockaddr *fwd_sa;
	int sender_sox = -1;
	if (!err) {
		//
		// First, from what sender address?
		if (recvmap->sox != in->local_sox) {
			//
			// Received on phone address; forward from general IPv6 address
			sender_sox = fwd->local_sox;
			fwd_sender_str = fwd->local_str;
			fwd_sa = fwd->local_sa;
		} else {
			//
			// Received from phone address? Then forward from mapped phone
			sender_sox = in->map_sox ((union sockaddr_in46 *) in->recv_sa, &fwd_sender_str, (union sockaddr_in46 *) &fwd_sa);
			if (sender_sox == -1) {
				//
				// Not received from phone.  Forward from general local address
				sender_sox = fwd->local_sox;
				fwd_sender_str = fwd->local_str;
				fwd_sa = fwd->local_sa;
			}
		}
		//
		// Second, to what target address?
		textptr_t route_fwdip;
		textptr_t route_fwdport;
		textnullify (&route_fwdip);
		textnullify (&route_fwdport);
		if (!resp) {
			if (!textisnull (&route_uri1)) {
				static const textptr_t param_nm [] = { { "received", 8 }, { "rport", 5 }, { NULL, 0 } };
				textptr_t * param_val [2];
				param_val [0] = &route_fwdip;
				param_val [1] = &route_fwdport;
				find_parameters_inheader (&route_uri1, param_nm, param_val);
			}
		}
		textptr_t via_host;
		uint16_t via_port;
		textnullify (&via_host);
		if (resp && !textisnull (&via1)) {
			//
			// Received as a response, with a Via: header to send to
			textptr_t via_transport;	// Ignored, known to be UDP
			err = err || !sip_components_invia (&via1, &via_transport, &via_host, &via_port);
			if ((!err) && (textptr2sockaddr (fwd->addrfam, &via_host, via_port, (union sockaddr_in46 *) fwd->send_sa) != 1)) {
				err = 1;
			}
		} else if (recvmap->sox != in->local_sox) {
			//
			// Received on faked phone address; target at mapped real phone address
			memcpy (fwd->send_sa, (fwd->addrfam == AF_INET6)? (void *) &recvmap->v6addr: (void *) &recvmap->v4addr, fwd->salen);
		} else if ((!resp) && !textisnull (&route_fwdip)) {
			//
			// Received on local_sox with 2nd Route: with received= parameter
			if (textptr2addr (fwd->addrfam, &route_fwdip, (void *) fwd->send_in) == 1) {
				uint16_t *port;
				if (fwd->addrfam == AF_INET) {
					port = &((struct sockaddr_in  *) fwd->send_sa)->sin_port ;
				} else {
					port = &((struct sockaddr_in6 *) fwd->send_sa)->sin6_port;
				}
				*port = htons (textptr2uint (&route_fwdport, 5060));
			} else {
				err = 1;
			}
		} else {
			//
			// TODO: If Route: headers exist, follow those? (Careful: Security, INVITE)
			//
			// Second attempt.
			//  - Is the Request URI directed towards an IPv6 address (and UDP port)?
			//  - Is there a generic uplink on the forwarding side?
			/* if ("...uri_ipv6[:port]...") { */
			/* 	"...use that..."; */
			/* } else */
			       if ((fwd->addrfam == AF_INET6)? seen_U: seen_u) {
				memcpy (fwd->send_sa, fwd->uplink_sa, fwd->salen);
			} else {
				sip_reply (&sip, 488, "No Generic Uplink Available", recvmap, in);
				return;
			}
		}
	}
	if (err) {
		syslog (LOG_CRIT, "Routing decision code returns an error\n");
		return;
	}
	if (seen_t) {
		char send_str [INET46_ADDRPORTSTRLEN];
		*soxcat (send_str, fwd->addrfam, (struct sockaddr *) fwd->send_sa, 5060) = '\0';
		printf ("\nRouting decision: %s over %s to %s via socket %d\n", resp? "Response": "Request", fwd->famname, send_str, fwd->local_sox);
	}

	char fwdbuf [SIP_MESSAGE_MAX_LENGTH + 100]; //TODO// Get this out of here -- and think about memoverlay
	char *pout = fwdbuf;

	//
	// ### RETRIEVE MESSAGE IDENTIFICATION INFO ###
	//
	if (textisnull (&callid) || textisnull (&from) || textisnull (&to)) {
		syslog (LOG_ERR, "Bad Request: call_id, from, to\n");
		sip_reply (&sip, 400, "Bad Request", recvmap, in);
		return;
	}
	textptr_t from_tag;
	textptr_t to_tag;
	textnullify (&from_tag);
	textnullify (&to_tag);
	static const textptr_t tag_names [] = { { "tag", 3 }, { NULL, 0 } };
	textptr_t *tag_val [1];
	tag_val [0] = &from_tag;
	find_parameters_inheader (&from, tag_names, tag_val);
	tag_val [0] = &to_tag;
	find_parameters_inheader (&to  , tag_names, tag_val);
	//
	// Are the required message components available?  CallId, From:tag, if resp then To:tag
	if (textisnull (&from_tag) || (resp && respcode.str && (respcode.len > 0 ) && (respcode.str [0] >= '2') && textisnull (&to_tag))) {
		syslog (LOG_ERR, "Bad Request: from_tag, [to_tag]\n");
		sip_reply (&sip, 400, "Bad Request", recvmap, in);
		return;
	}

	//
	// ### REWRITE MESSAGE START-LINE ###
	//
	if (resp) {
		pout = txtcat (pout, &t_sip20);
		pout = txtcat (pout, &t_space);
		pout = txtcat (pout, &respcode);
		pout = txtcat (pout, &t_space);
		pout = txtcat (pout, &respdescr);
		pout = txtcat (pout, &t_newline);
	} else {
		pout = txtcat (pout, &method);
		pout = txtcat (pout, &t_space);
		pout = in->mapcat_uri_host (pout, &requri, false);
		pout = txtcat (pout, &t_space);
		pout = txtcat (pout, &t_sip20);
		pout = txtcat (pout, &t_newline);
	}

	//
	// ### INSERT LOCAL VIA AND TWO-SIDED RECORD-ROUTE HEADERS ###
	//
	// Via: for the outgoing side is used for replies
	int drop_vias = 0;
	if (resp) {
		drop_vias = 1;
	} else {
		/* Find the branch in the top Via: header in the input */
		textptr_t via0_branch;
		textnullify (&via0_branch);
		static const textptr_t  parm_nams [2] = { { "branch", 6 }, { NULL, 0 } };
		textptr_t *parm_vals [1];
		parm_vals [0] = &via0_branch;
		find_parameters_inheader (&via0, parm_nams, parm_vals);
		/* Insert a Via: header with the same branch as the input */
		pout = txtcat (pout, &t_via);
		pout = txtcat (pout, &t_colonspace);
		pout = txtcat (pout, &t_sip20udp);
		pout = txtcat (pout, &t_space);
		pout = txtcat (pout, &fwd_sender_str);
		/* Produce a branch parameter in the new Via: header */
		if (textisnull (&via0_branch)) {
			  // No branch at all, we a re stateless.
			; // Issue #10 explains the problems with...
			  // pout = intcat (pout, random ());
		} else {
			pout = txtcat (pout, &t_semicolon_branch_is);
			pout = txtcat (pout, &via0_branch);
		}
		pout = txtcat (pout, &t_newline);
	}
	//
	// Record-Route: one is the outgoing side (for return transactions)
	// Record-Route: two is the incoming side (for forward transactions)
	if ((!resp) && textinlist (&method, &t_invite, &t_subscribe, &nulltext)) {
		//
		// Produce the Record-Route: header name
		pout = txtcat (pout, &t_recordroute);
		pout = txtcat (pout, &t_colonspace);
		//
		// Produce the outgoing side URI
		pout = txtcat (pout, &t_urlbra_sip);
		pout = soxcat (pout, fwd->addrfam, fwd_sa, -1);
		pout = txtcat (pout, &t_semicolon_lr);
		//NOTHERE// pout = txtcat (pout, &t_semicolon_received_is);
		//NOTHERE// pout = soxcat (pout, fwd->addrfam, fwd->recv_sa, 0);
		//NOTHERE// pout = txtcat (pout, &t_semicolon_rport_is);
		//NOTHERE// pout = intcat (pout, ntohs (*fwd->recv_port));
		pout = txtcat (pout, &t_urlket);
		pout = txtcat (pout, &t_commaspace);
		//
		// Produce the incoming side URI
		pout = txtcat (pout, &t_urlbra_sip);
		strcpy (pout, recvmap->sox_str);
		pout += strlen (pout);
		pout = txtcat (pout, &t_semicolon_lr);
		pout = txtcat (pout, &t_semicolon_received_is);
		pout = soxcat (pout, in->addrfam, in->recv_sa, 0);
		pout = txtcat (pout, &t_semicolon_rport_is);
		pout = intcat (pout, ntohs (*in->recv_port));
		pout = txtcat (pout, &t_urlket);
		pout = txtcat (pout, &t_newline);
	}
	int drop_routes = 2;

	//
	// ### REWRITE MESSAGE HEADERS (INCORPORATED MAPPING REGISTRY) ###
	//
	if (sip_firstheader (&sip, &h_nm, &h_val)) do {
		if (textinlist (&h_nm, &t_contentlength, &nulltext)) {
			// Header to be removed (so: do nothing)
			// RFC3261: Content-length.
#if 0
printf ("DEBUG: Removing %.*s: %.*s\n", h_nm.len, h_nm.str, h_val.len, h_val.str);
#endif
			;
		} else if (textinlist (&h_nm, &nulltext)) {
			// Header containing only a URI (so: map it)
			// RFC3261: None.
#if 0
printf ("DEBUG: Mapping URI %.*s: %.*s\n", h_nm.len, h_nm.str, h_val.len, h_val.str);
#endif
			pout = txtcat (pout, &h_nm);
			pout = txtcat (pout, &t_colonspace);
			pout = in->mapcat_uri_host (pout, &h_val, true);
			pout = txtcat (pout, &t_newline);
		} else if (textinlist (&h_nm, &t_alertinfo, &t_callinfo, &t_errorinfo, &t_from, &t_inreplyto, &t_replyto, &t_to, &t_passertedidentity, &t_ppreferredidentity, &t_path, &t_passociateduri, &t_pcalledpartyid, &t_referto, &t_serviceroute, &t_referredby, &t_historyinfo, &t_pprofilekey, &t_prefusedurilist, &t_permissionmissing, &t_triggerconsent, &t_pserveduser, &t_geolocation, &nulltext) || ((!contact_wrapped) && texteq (&h_nm, &t_contact))) {
			// Header containing one or more URIs, whole header or <...> <...>
			// RFC3261: Alert-info, Call-info, Contact, Error-info, From,
			//	In-reply-to, Reply-to, To.
			// (Note that Contact may be skipped if the flag
			//  contact_wrapped is set.)
			// RFC3325: P-asserted-identity, P-preferred-identity.
			// RFC3327: Path.
			// RFC3455: P-associated-uri, P-called-party-id.
			// RFC3515: Refer-to.
			// RFC3608: Service-route.
			// RFC3892: Referred-by.
			// RFC4244: History-info.
			// RFC5360: Permission-missing, Trigger-consent.
			// RFC6442: Geolocation.
			//TODO// Perhaps Contact needs special ;ct= treatment
#if 0
printf ("DEBUG: Mapping multi-URI %.*s: %.*s\n", h_nm.len, h_nm.str, h_val.len, h_val.str);
#endif
			// Iterate over URIs, and care for surrounding filler text
			textptr_t next = { h_nm.str, 0 };
			textptr_t uri;
			if (sip_firsturi_inheader (&h_val, &uri)) do {
				// Print any filler before the URI
				next.len = ((intptr_t) uri.str) - ((intptr_t) next.str);
				if (next.len > 0) {
					pout = txtcat (pout, &next);
					// Prepare for a filler after the URI
					next.str = uri.str + uri.len;
				}
				// Translate the URI
				pout = in->mapcat_uri_host (pout, &uri, true);
			} while (sip_nexturi_inheader (&h_val, &uri));
			// Print any filler after the last URI
			next.len = ((intptr_t) h_val.str) + h_val.len - ((intptr_t) next.str);
			if (next.len > 0) {
				pout = txtcat (pout, &next);
			}
			// End the line of this translated header
			pout = txtcat (pout, &t_newline);
		} else if (texteq (&h_nm, &t_maxforwards)) {
			// Special treatment: lower, possibly drop message
			// RFC3261: Max-forwards.
#if 0
printf ("DEBUG: Lowering %.*s: %.*s\n", h_nm.len, h_nm.str, h_val.len, h_val.str);
#endif
			uint16_t maxfwd = 0;
			uint16_t idx = 0;
			while ((idx < h_val.len) && (h_val.str [idx] >= '0') && (h_val.str [idx] <= '9')) {
				maxfwd *= 10;
				maxfwd += h_val.str [idx++] - '0';
			}
			if (maxfwd == 0) {
				// Break off forwarding, send an error reply
				sip_reply (&sip, 483, "Too Many Hops", recvmap, in);
				return;
			}
			// Lower max-forwarding by 1 and create a modified header
			maxfwd--;
			pout = txtcat (pout, &h_nm);
			pout += sprintf (pout, ": %d\r\n", maxfwd);
		//TODO// Consider treating Proxy-require especially
		//TODO// Could take notice of Content-type to check for SDP
		} else {
			// Header to be copied literally (default action)
			// Catchall: X-*
			// RFC3261: Accept, Accept-encoding, Accept-language, Allow,
			//	Authentication-info, Authorization, Call-id,
			//	Content-disposition, Content-encoding, Content-language,
			//	Content-type, Cseq, Date, Expires, Mime-version,
			//	Min-expires, Organization, Priority, Proxy-authenticate,
			//	Proxy-authorization, Proxy-require, Record-route,
			//	Record-route, Require, Retry-after, Route, Server, Subject,
			//	Supported, Timestamp, Unsupported, User-agent, Via,
			//	Warning, Www-authenticate.
			// RFC3262: Rack, Rseq.
			// RFC3313: P-media-authorization.
			// RFC3323: Privacy.
			// RFC3326: Reason.
			// RFC3329: Security-client, Security-server, Security-verify.
			// RFC3455: P-visited-network-id, P-access-network-info,
			//	P-charging-addr, P-charging-vector.
			// RFC3841: Accept-contact, Reject-contact, Request-disposition.
			// RFC3891: Replaces.
			// RFC3903: Sip-etag, Sip-if-match.
			// RFC3911: Join.
			// RFC4028: Min-se, Session-expires.
			// RFC4412: Accept-resource-priority, Resource-priority.
			// RFC4457: P-user-database.
			// RFC4474: Identity, Identity-info.
			// RFC4488: Refer-sub.
			// RFC4538: Target-dialog.
			// RFC4964: P-answer-state.
			// RFC5009: P-early-media.
			// RFC5373: Answer-mode, Priv-answer-mode.
			// RFC5393: Max-breadth.
			// RFC5503: P-dcs-billing-info, P-dcs-laes, P-dcs-osps,
			//	P-dcs-redirect, P-dcs-trace-party-id.
			// RFC5626: Flow-timer.
			// RFC5839: Suppress-if-match.
			// RFC6050: P-asserted-service, P-preferred-service.
			// RFC6086: Info-package, Recv-info.
			// RFC6442: Geolocation-routing, Geolocation-error.
			// RFC6665: Event, Allow-events, Subscription-state.
			bool skipme = 0;
			if (drop_routes && texteq (&h_nm, &t_route)) {
#if 0
printf ("DEBUG: Drop %d routes in %.*s\n", drop_routes, (int) h_val.len, h_val.str);
#endif
				routeN = (drop_routes-- == 2) ? &route0 : &route1;
				memcpy (&h_val, routeN, sizeof (t_route));
				skipme = (h_val.len == 0);
#if 0
printf ("DEBUG: Remaining (skipme=%d) routes are %.*s\n", skipme, (int) h_val.len, h_val.str);
#endif
			} else if (drop_vias && texteq (&h_nm, &t_via)) {
#if 0
printf ("DEBUG: Dropping %.*s: %.*s\n", h_nm.len, h_nm.str, h_val.len, h_val.str);
#endif
				// Via: headers contain one chunk of info each
				drop_vias--;
				skipme = 1;
			}
			if (!skipme) {
#if 0
printf ("DEBUG: Cloning %.*s: %.*s\n", h_nm.len, h_nm.str, h_val.len, h_val.str);
#endif
				pout = txtcat (pout, &h_nm);
				pout = txtcat (pout, &t_colonspace);
				pout = txtcat (pout, &h_val);
				pout = txtcat (pout, &t_newline);
			}
		}
	} while (sip_nextheader (&sip, &h_nm, &h_val));
	//
	// Analyse the content a little more, if any
	bool have_sdp, have_other;
	have_sdp = (!textisnull (&conttp)) && (!textisnull (&attachment)) && texteq (&conttp, &t_applicationsdp) && (memcmp (attachment.str, "v=0", 3) == 0);
	have_other = (!textisnull (&conttp)) && (!textisnull (&attachment)) && !texteq (&conttp, &t_applicationsdp);
	//
	// Insert Content-Length header (leave up to 4 digits dynamic info for sdp)
	char *sdplen_end = NULL;
	pout = txtcat (pout, &t_contentlength);
	pout = txtcat (pout, &t_colonspace);
	if (have_sdp) {
		//
		// SDP needs variable space, up to 4 digits should suffice
		pout = txtcat (pout, &t_3pos0);
		// Include 1 optional space after the colon
		sdplen_end = pout;
	} else if (have_other) {
		pout = intcat (pout, attachment.len);
	} else {
		pout = txtcat (pout, &t_0);
	}
	pout = txtcat (pout, &t_newline);
	//
	// Separate content from the headers with an empty line
	pout = txtcat (pout, &t_newline);
	char *content_start = pout;

	//
	// ### REWRITE SDP BODY (IF PRESENT)
	//
	if (have_sdp) {
		// Determine if RTP-control actions are needed for this body
		bool sdp_enter = false, sdp_exit = false;
		if (resp) {
			// Responses. 200-: sdp_enter; 400+: sdp_exit.
			sdp_enter = respcode.str [0] <= '2';
			sdp_exit  = respcode.str [0] >= '4';
		} else {
			// Requests. INVITE: sdp_enter; BYE, CANCEL: sdp_exit.
			sdp_enter = texteq (&method, &t_invite);
			sdp_exit  = texteq (&method, &t_bye) || texteq (&method, &t_cancel);
		}
		//
		// Regardless of RTP action, rewrite SDP bodies
		if (textisnull (&attachment)) {
			attachment.len = 0;
		}
		//
		// Find the c= default value that may occur before m=
		textptr_t c_session;
		sdp_chase_c_until_m (&attachment, &c_session);
		//
		// Copy series of lines separated by m= lines
		textptr_t line;
		do {
			//
			// Copy one series of lines until m=
			while (sdp_fetch_line (&attachment, &line) && (*line.str != 'm')) {
				if ((line.len > 12) && (memcmp (line.str, in->sdp_c_first9, 9) == 0)) {
					// Copy c= lines with IP translation
					pout = strcpy (pout, fwd->sdp_c_first9);
					pout += 9;
					line.str += 9;
					line.len -= 9;
					while ((line.len > 0) && (line.str [line.len - 1] == '\r') || (line.str [line.len - 1] == '\n')) {
						line.len--;
					}
					bool ok = (line.len > 7);
					// Construct a zero-port socket address from a lone address
					if (ok) {
						uint8_t tmpadr [16];
						if (in->addrfam == AF_INET) {
							ok = textptr2addr (AF_INET , &line, tmpadr);
						} else if (in->addrfam == AF_INET6) {
							ok = textptr2addr (AF_INET6, &line, tmpadr);
						} else {
							ok = 0;
						}
					}
					if (ok) {
						inet_ntop (fwd->addrfam, (void *) fwd->local_in, pout, INET46_ADDRSTRLEN);
						pout += strlen (pout);
					}
					if (!ok) {
						pout = txtcat (pout, &line);
					}
					pout = txtcat (pout, &t_newline);
				} else {
					// Copy other lines than m= or c= literally
					pout = txtcat (pout, &line);
				}
			}
			//
			// Now find a pair of m= and c= and RTP-map those
			if (!textisnull (&line) && (*line.str == 'm')) {
				textptr_t c_media;
				sdp_chase_c_until_m (&attachment, &c_media);
				if (textisnull (&c_media)) {
					memcpy (&c_media, &c_session, sizeof (textptr_t));
				}
				if (!textisnull (&c_media)) {
					//
					// Find the RTP-port in the  m= line
					textptr_t m_port;
					int rtpport = 0;
					int portpos = 2;
					while ((portpos < line.len) && (line.str [portpos] != ' ')) {
						portpos++;
					}
					while ((portpos < line.len) && (line.str [portpos] == ' ')) {
						portpos++;
					}
					int restpos = portpos;
					while ((line.str [restpos] >= '0') && (line.str [restpos] <= '9')) {
						restpos++;
					}
					m_port.str = line.str + portpos;
					m_port.len = ((intptr_t) restpos) - ((intptr_t) portpos);
					if (m_port.len == 0) {
						syslog (LOG_ERR, "Bad Request: m_port.len == 0\n");
						sip_reply (&sip, 400, "Bad Request", recvmap, in);
						return;
					}
					//
					// Find the IP address in the c= line
					textptr_t c_ip;
					uint8_t mediaip [16];
					c_ip.str = c_media.str + 9;
					c_ip.len = c_media.len - 9;
					while ((c_ip.len > 0) && (c_ip.str [c_ip.len - 1] <= ' ')) {
						c_ip.len--;
					}
					if (!textptr2addr (in->addrfam, &c_ip, mediaip)) {
						//
						// No actual IP in request, pass it on literally
						pout = txtcat (pout, &line);
					} else {
						//
						// Map RTP for c= and m= as a pair:
						// 1. Create or update or a mapping in RTPproxy64
						if (sdp_enter) {
							rtpport = rtpctl_update (in->addrfam, (resp?fwd:in)->addrfam, &c_ip, &m_port, &callid, &from_tag, &to_tag);
							syslog (LOG_INFO, "Expecting media on port %d over %s\n", rtpport, fwd->famname);
						}
						//
						// Map RTP for c= and m= as a pair:
						// 2. Delete a mapping in RTPproxy64
						if (sdp_exit) {
							syslog (LOG_INFO, "Stopping RTP traffic between From: tag %.*s and To: tag %.*s", from_tag.len, from_tag.str, to_tag.len, to_tag.str);
							rtpctl_delete (&callid, &from_tag, &to_tag);
							rtpctl_delete (&callid, &to_tag, &from_tag);
						}
						//
						// Write out modified m= line
						memcpy (pout, line.str, portpos);
						pout += portpos;
						pout = intcat (pout, rtpport);
						memcpy (pout, line.str + restpos, line.len - restpos);
						pout += line.len - restpos;
					}
				}
			}
		} while (sdp_fetch_line (&attachment, &line));
		//
		// We need to produce our dynamic length in the allotted room
		// Normally, SDP length is 3, and SIP commonly discards 9999+
		int sdplen = ((intptr_t) pout) - ((intptr_t) content_start);
		if (sdplen > 999) {
			sdplen_end [-4] = '0' + ((sdplen / 1000)     );
		}
		if (sdplen >  99) {
			sdplen_end [-3] = '0' + ((sdplen /  100) % 10);
		}
		if (sdplen >   9) {
			sdplen_end [-2] = '0' + ((sdplen /   10) % 10);
		}
		if (sdplen >=  0) {
			sdplen_end [-1] = '0' + ((sdplen       ) % 10);
		}
	} else if (have_other) {
		// Non-SDP body: clone literally
		pout = txtcat (pout, &attachment);
	}

	//
	// ### SEND THE MODIFIED SIP/[SDP] MESSAGE ###
	//
	ssize_t fwdbuflen = ((intptr_t) pout) - ((intptr_t) fwdbuf);
	if (seen_t) {
		printf ("%.*s\n", (int) fwdbuflen, fwdbuf);
		if (--seen_t) {
			return;
		} else {
			exit (0);
		}
	}
	ssize_t sent = sendto (sender_sox, fwdbuf, fwdbuflen, MSG_EOR, fwd->send_sa, fwd->salen);
	if (sent < fwdbuflen) {
		syslog (LOG_ERR, "Sent only %zd out of %zd bytes\n", sent, fwdbuflen);
	}
}


/* Handle RTP traffic going from IPv4 to IPv6, according to the given map.
 * There is a separate function for the opposite direction, for efficiency's sake.
 */
void handle_rtp_4to6 (struct v4v6rtpmap *rtpmap) {
	//
	// Read an RTP packet, but only from the accepted sender
	char buf [1002];
	struct sockaddr_in sender;
	socklen_t senderlen = sizeof (sender);
	ssize_t buflen = recvfrom (rtpmap->v4sox, buf, 1000, MSG_DONTWAIT,
			(struct sockaddr *) &sender, &senderlen);
	if (buflen > 0 && senderlen == sizeof (rtpmap->v4remote) &&
				 !memcmp (&rtpmap->v4remote, &sender, senderlen)) {
		ssize_t sent = sendto (rtpmap->v6sox, buf, buflen, MSG_EOR,
			(struct sockaddr *) &rtpmap->v6remote, sizeof (rtpmap->v6remote));
		//
		// Update the RTPmap expiration timer
		rtpmap->timeout = time (NULL) + 60;
		if (sent < buflen) {
			syslog (LOG_ERR, "Passed only %zd out of %zd RTP bytes from IPv4 to IPv6: %s\n", sent, buflen, strerror (errno));
		}
	} else {
		syslog (LOG_ERR, "RTP not mapped from IPv4 to IPv6 due to sender mismatch\n");
	}
}


/* Handle RTP traffic going from IPv6 to IPv4, according to the given map.
 * There is a separate function for the opposite direction, for efficiency's sake.
 */
void handle_rtp_6to4 (struct v4v6rtpmap *rtpmap) {
	//
	// Read an RTP packet, but only from the accepted sender
	char buf [1002];
	struct sockaddr_in6 sender;
	socklen_t senderlen = sizeof (sender);
	ssize_t buflen = recvfrom (rtpmap->v6sox, buf, 1000, MSG_DONTWAIT,
			(struct sockaddr *) &sender, &senderlen);
	if (buflen > 0 && senderlen == sizeof (rtpmap->v6remote) &&
				 !memcmp (&rtpmap->v6remote, &sender, senderlen)) {
		ssize_t sent = sendto (rtpmap->v4sox, buf, buflen, MSG_EOR,
			(struct sockaddr *) &rtpmap->v4remote, sizeof (rtpmap->v4remote));
		//
		// Update the RTPmap expiration timer
		rtpmap->timeout = time (NULL) + 60;
		if (sent < buflen) {
			syslog (LOG_ERR, "Passed only %zd out of %zd RTP bytes from IPv6 to IPv4: %s\n", sent, buflen, strerror (errno));
		}
	} else {
		syslog (LOG_ERR, "RTP not mapped from IPv6 to IPv4 due to sender mismatch\n");
	}
}


/* Run the proxy daemon.  Listen to all the sockets that have been opened, and
 * upon arrival of a SIP message, handle it.
 *
 * The scheduling algorithm handles both SIP and RTP, in a straightforward fashion:
 *  - a single loop scans for traffic on SIP and RTP ports
 *  - in each loop, _all_ RTP traffic is handled first to make it faster than SIP
 *  - in each loop, _one_ SIP message is handled last, to make it yield to    RTP
 */
void run_proxy (void) {
	int nfds = 0;
	struct v4v6map    *sipmap = v4v6maplist;
	struct v4v6rtpmap *rtpmap = NULL;
	struct v4v6rtpmap **checkrtp = NULL;
	//
	// No RTP map initialisation needed; it is initially empty
	while (sipmap) {
		if (sipmap->sox >= nfds) {
			nfds = sipmap->sox + 1;
		}
		sipmap = sipmap->next;
	}
	//
	// Be a good daemon.  Loop forever.  Build & Handle <--> Wait & Listen.
	fd_set fdset;
	FD_ZERO (&fdset);
	while (1) {		// Loop until all SIP and RTP sockets are closed
		changed_rtpmap = 0;
		//
		// Build an fd_set of RTP sockets to listen to, handling all
		rtpmap = v4v6rtpmap;
		while (rtpmap) {
			if (rtpmap->v4sox != -1) {
				if (FD_ISSET (rtpmap->v4sox, &fdset)) {
					handle_rtp_4to6 (rtpmap);
				} else {
					FD_SET (rtpmap->v4sox, &fdset);
					if (rtpmap->v4sox >= nfds) {
						nfds = rtpmap->v4sox + 1;
					}
				}
			}
			if (rtpmap->v6sox != -1) {
				if (FD_ISSET (rtpmap->v6sox, &fdset)) {
					handle_rtp_6to4 (rtpmap);
				} else {
					FD_SET (rtpmap->v6sox, &fdset);
					if (rtpmap->v6sox >= nfds) {
						nfds = rtpmap->v6sox + 1;
					}
				}
			}
			rtpmap = rtpmap->next;
		}
		//
		// Build an fd_set of SIP sockets to listen to, possibly handling one
		sipmap = v4v6maplist;
		while (sipmap) {
			if (sipmap->sox != -1) {
				if (FD_ISSET (sipmap->sox, &fdset)) {
					handle_sip (sipmap);
					fflush (stdout); //DEBUG
					//
					// One SIP message done, now yield to RTP
					while (sipmap) {
						if (sipmap->sox != -1) {
							FD_SET (sipmap->sox, &fdset);
						}
						sipmap = sipmap->next;
					}
					break;
				} else {
					FD_SET (sipmap->sox, &fdset);
				}
			}
			sipmap = sipmap->next;
		}
		//
		// If SIP has changed the RTP map, update the fd_set
		if (changed_rtpmap) {
			FD_ZERO (&fdset);
			continue;	// Loop again to recreate the fd_set
		}
		//
		// Check one RTP map each loop, and remove it after it has expired
		if (!checkrtp || !*checkrtp) {
			checkrtp = &v4v6rtpmap;
		}
		struct v4v6rtpmap *victim = *checkrtp;
		if (victim && victim->timeout < time (NULL)) {
			//
			// Timeout on pair of RTPmap entries
			*checkrtp = victim->next->next;
			if (victim->v4sox != -1) {
				FD_CLR (victim [0].v4sox, &fdset);
				FD_CLR (victim [1].v4sox, &fdset);
				close (victim [0].v4sox);
				close (victim [1].v4sox);
			}
			if (victim->v6sox != -1) {
				FD_CLR (victim [0].v6sox, &fdset);
				FD_CLR (victim [1].v6sox, &fdset);
				close (victim [0].v6sox);
				close (victim [1].v6sox);
			}
			free (victim);
		} else if (victim) {
			//
			// No timeout.  Next time check another RTPmap entry pair
			checkrtp = & (*checkrtp)->next->next;
		}
		//
		// End looping if there are no SIP or RTP sockets left to wait for
		if (signaled_HUP && v4v6rtpmap == NULL) {
			break;						// Terminate loop when done
		}
		//
		// Now listen to the sockets in the fd_set and process output
		struct timespec t_1sec = { 1, 0 };
		int ready = pselect (nfds, &fdset, NULL, NULL,
					v4v6rtpmap? &t_1sec: NULL,	// Time if RTP sessions exist
					NULL				// Signal set
				);
		if (ready == -1) {
			FD_ZERO (&fdset);				// Exception, usually SIGHUP
			if (errno == EINTR) {
				errno = 0;
			} else {
				syslog (LOG_ERR, "%s: Error in pselect(): %s\n", program, strerror (errno));
			}
		}
		//
		// The next loop iteration will handle, and set bits as it runs
	}
}


/* Create sockets and try to listen to the various addresses presented.
 * Demand that precisely one endpoint of each mapping binds properly.
 */
void open_sockets (void) {
	int sane = 1;
	int i;
	struct v4v6map *map = v4v6maplist;
	for (i=0; i<v4v6maplen; map=map->next, i++) {
		int v4sox, v6sox;
		v4sox = socket (PF_INET,  SOCK_DGRAM, 0);
		v6sox = socket (PF_INET6, SOCK_DGRAM, 0);
		if ((v4sox == -1) || (v6sox == -1)) {
			fprintf (stderr, "%s: Failed to allocate sockets: %s\n", program, strerror (errno));
			sane = 0;
			continue;
		}
		if ((map->v4addr.sin_port  == 0) || (bind (v4sox, (struct sockaddr *) &map->v4addr, sizeof (struct sockaddr_in )) != 0)) {
			close (v4sox);
			v4sox = -1;
		}
		if ((map->v6addr.sin6_port == 0) || (bind (v6sox, (struct sockaddr *) &map->v6addr, sizeof (struct sockaddr_in6)) != 0)) {
			close (v6sox);
			v6sox = -1;
		}
		if ((v4sox == -1) && (v6sox == -1)) {
			char v4ip [INET46_ADDRPORTSTRLEN];
			char v6ip [INET46_ADDRPORTSTRLEN];
			*soxcat (v4ip, AF_INET,  (struct sockaddr *) &map->v4addr, 5060) = '\0';
			*soxcat (v6ip, AF_INET6, (struct sockaddr *) &map->v6addr, 5060) = '\0';
			fprintf (stderr, "%s: Both IPv4 address %s and IPv6 address %s in one mapping are remote\n", program, v4ip, v6ip);
			sane = 0;
			continue;
		}
		if ((v4sox != -1) && (v6sox != -1)) {
			char v4ip [INET46_ADDRPORTSTRLEN];
			char v6ip [INET46_ADDRPORTSTRLEN];
			*soxcat (v4ip, AF_INET,  (struct sockaddr *) &map->v4addr, 5060) = '\0';
			*soxcat (v6ip, AF_INET6, (struct sockaddr *) &map->v6addr, 5060) = '\0';
			fprintf (stderr, "%s: Both IPv4 address %s and IPv6 address %s in one mapping are local\n", program, v4ip, v6ip);
			sane = 0;
			continue;
		}
		if (v4sox != -1) {
			map->sox = v4sox;
			char *pout = map->sox_str;
			pout = soxcat (pout, AF_INET,  (struct sockaddr *) &map->v4addr, 5060);
			map->flags = MAP_V4BOUND;
			if (seen_t) {
				printf ("Bound socket %d to IPv4 address %s\n", v4sox, map->sox_str);
			}
			if (memcmp (&map->v4addr, &v4local_sa, sizeof (map->v4addr)) == 0) {
				v4param.local_sox = v4sox;
			}
		} else {
			map->sox = v6sox;
			char *pout = map->sox_str;
			pout = soxcat (pout, AF_INET6, (struct sockaddr *) &map->v6addr, 5060);
			map->flags = MAP_V6BOUND;
			if (seen_t) {
				printf ("Bound socket %d to IPv6 address %s\n", v6sox, map->sox_str);
			}
			if (memcmp (&map->v6addr, &v6local_sa, sizeof (map->v6addr)) == 0) {
				v6param.local_sox = v6sox;
			}
		}
	}
	if (!sane) {
		exit (1);
	}
}


/* Close all SIP sockets.  This is done in the parent after forking, as well as in
 * the child after receiving a SIGHUP.  The latter makes it possible for the child
 * to continue serving outstanding RTP sessions, while allowing a new sipproxy64
 * to be started to handle any new SIP traffic.  Since SIP is handled as UDP, a
 * quick restart like this will not even be noticed by clients!
 */
void close_sockets (void) {
	struct v4v6map *map = v4v6maplist;
	while (map) {
		if (map->sox != -1) {
			if (map->sox == v4param.local_sox) {
				v4param.local_sox = -1;
			}
			if (map->sox == v6param.local_sox) {
				v6param.local_sox = -1;
			}
			close (map->sox);
			map->sox = -1;
		}
		map = map->next;
	}
	if (v4param.local_sox != -1) {
		close (v4param.local_sox);
		v4param.local_sox = -1;
	}
	if (v6param.local_sox != -1) {
		close (v6param.local_sox);
		v6param.local_sox = -1;
	}
}


/* Command line parsing, argument processing, resource setup.
 */
void open_cmdline (int argc, char *argv []) {
	//
	// Basic initialisation
	program = argv [0];
	memset (&v4uplink_sa, 0, sizeof (v4uplink_sa));
	memset (&v6uplink_sa, 0, sizeof (v6uplink_sa));
	memset (&v4local_sa,  0, sizeof (v4local_sa ));
	memset (&v6local_sa,  0, sizeof (v6local_sa ));
	memset (&v4recv_sa,   0, sizeof (v4recv_sa  ));
	memset (&v6recv_sa,   0, sizeof (v6recv_sa  ));
	memset (&v4send_sa,   0, sizeof (v4send_sa  ));
	memset (&v6send_sa,   0, sizeof (v6send_sa  ));
	//TODO:OLD// v4local_sa.sin_family  = v4uplink_sa.sin_family  =
	//TODO:OLD// v4recv_sa.sin_family   = v4send_sa.sin_family    = AF_INET ;
	//TODO:OLD// v6local_sa.sin6_family = v6uplink_sa.sin6_family =
	//TODO:OLD// v6recv_sa.sin6_family  = v6send_sa.sin6_family   = AF_INET6;
	//TODO:OLD// v4recv_sa.sin_port     = v4send_sa.sin_port      = htons (5060);
	//TODO:OLD// v6recv_sa.sin6_port    = v6send_sa.sin6_port     = htons (5060);
	//
	// Process commandline arguments
	int sane = 1;
	textptr_t *v4bad = NULL, *v6bad = NULL;
	int seen_l = 0, seen_L = 0, seen_h = 0;
	char *arg_b = NULL, *arg_B = NULL, *arg_p = NULL, *arg_P = NULL;
	int opt;
	while (opt = getopt_long (argc, argv, short_opts, long_opts, NULL), opt != -1) {
		switch (opt) {
		case 'l':
			seen_l++;
			v4param.local_str.str = strdup (optarg);
			v4param.local_str.len = strlen (optarg);
			if (!textptr2sockaddr (AF_INET,  &v4param.local_str, 5060, (union sockaddr_in46 *) &v4local_sa)) {
				v4bad = &v4param.local_str;
			}
			break;
		case 'L':
			seen_L++;
			v6param.local_str.str = strdup (optarg);
			v6param.local_str.len = strlen (optarg);
			if (!textptr2sockaddr (AF_INET6, &v6param.local_str, 5060, (union sockaddr_in46 *) &v6local_sa)) {
				v6bad = &v6param.local_str;
			}
			break;
		case 'u':
			seen_u++;
			v4param.uplink_str.str = strdup (optarg);
			v4param.uplink_str.len = strlen (optarg);
			if (!textptr2sockaddr (AF_INET,  &v4param.uplink_str, 5060, (union sockaddr_in46 *) &v4uplink_sa)) {
				v4bad = &v4param.uplink_str;
			}
			break;
		case 'U':
			seen_U++;
			v6param.uplink_str.str = strdup (optarg);
			v6param.uplink_str.len = strlen (optarg);
			if (!textptr2sockaddr (AF_INET6, &v6param.uplink_str, 5060, (union sockaddr_in46 *) &v6uplink_sa)) {
				v6bad = &v6param.uplink_str;
			}
			break;
		case 'w':
			if (contact_wrapped) {
				fprintf (stderr, "%s: You may only specify -w or --wrapped once\n", program);
				sane = 0;
			}
			contact_wrapped = 1;
			break;
		case 't':
			seen_t++;
			break;
		case 'h':
		case '?':
			sane = 0;
			seen_h = 1;
			break;
		default:
			fprintf (stderr, "%s: Unrecognised arguments: %s...\n", program, argv [optind]);
			sane = 0;
			break;
		}
	}
	if (!sane) {
		fprintf (stderr, "Usage: %s -l <v4local> -L <v6local> [-u <v4uplink>] [-U <v6uplink>] [-w] [<v4phone>=<v6phone> ...]\n", program);
		exit (!seen_h);
	}
	//
	// Sanity check on received input
	char **phonev = argv + optind;
	int phonec = argc - optind;
	if (seen_l != 1) {
		fprintf (stderr, "%s: You must specify one IPv4 local address of this proxy using -l\n", program);
		sane = 0;
	}
	if (seen_L != 1) {
		fprintf (stderr, "%s: You must specify one IPv6 local address of this proxy using -L\n", program);
		sane = 0;
	}
	if (seen_u > 1) {
		fprintf (stderr, "%s: You may not specify multiple IPv4 uplink addresses using -u\n", program);
		sane = 0;
	}
	if (seen_U > 1) {
		fprintf (stderr, "%s: You may not specify multiple IPv6 uplink addresses using -U\n", program);
		sane = 0;
	}
	if ((phonec == 0) && ((!seen_u) || (!seen_U))) {
		fprintf (stderr, "%s: You should define phones and/or use uplinks on both ends\n", program);
		sane = 0;
	}
	if (!sane) {
		exit (1);
	}
	if (v4bad) {
		fprintf (stderr, "%s: Bad IPv4 address: %.*s\n", program, v4bad->len, v4bad->str);
		sane = 0;
	}
	if (v6bad) {
		fprintf (stderr, "%s: Bad IPv6 address: %.*s\n", program, v6bad->len, v6bad->str);
		sane = 0;
	}
	if (!sane) {
		exit (1);
	}
	//
	// Build a list of address mappings
	while (phonec-- > 0) {
		struct sockaddr_in  v4phone;
		struct sockaddr_in6 v6phone;
		char *equals = strchr (phonev [phonec], '=');
		if (!equals) {
			fprintf (stderr, "%s: Phone address map should comprise of IPv4=IPv6 arguments, failed on: %s\n", program, phonev [phonec]);
			sane = 0;
			continue;
		}
		if (phonev [phonec] [1] == '\0') {
			/* Quietly skip an entry, deliberately consuming the default port number */
			continue;
		}
		*equals = 0;
		textptr_t v4ip;
		textptr_t v6ip;
		v4ip.str = phonev [phonec];
		v4ip.len = ((intptr_t) equals) - ((intptr_t) phonev [phonec]);
		v6ip.str = equals + 1;
		v6ip.len = strlen (equals + 1);
		uint16_t defaultport = 5060 + 2 + 2 * phonec;
		if (!textptr2sockaddr (AF_INET, &v4ip, defaultport, (union sockaddr_in46 *) &v4phone)) {
			fprintf (stderr, "%s: Phone map should start with IPv4 address or address:port, failed on: %.*s\n", program, v4ip.len, v4ip.str);
			sane = 0;
		}
		if (!textptr2sockaddr (AF_INET6, &v6ip, defaultport, (union sockaddr_in46 *) &v6phone)) {
			fprintf (stderr, "%s: Phone map should start with IPv6 address or address:port, failed on: %.*s\n", program, v6ip.len, v6ip.str);
			sane = 0;
		}
		insert_mapping (&v4ip, &v4phone, &v6ip, &v6phone);
	}
	//
	// Traffic destined for the IPv4 uplink may be sent to the IPv6 listen address
	insert_mapping (&v4param.uplink_str, &v4uplink_sa,
			&v6param.local_str,  &v6local_sa);
	//
	// Traffic destined for the IPv6 uplink may be sent to the IPv4 listen address
	insert_mapping (&v4param.local_str,  &v4local_sa,
			&v6param.uplink_str, &v6uplink_sa);
	//
	// Final check before returning control
	if (!sane) {
		exit (1);
	}
}


/* Signal handler for SIGHUP.  Stop handling new SIP messages, and permit another daemon
 * to start doing that.  Meanwhile, any open RTP sessions are supported until they are
 * closed.  Note that RTP sessions automatically close after 60 seconds of inactivity;
 * as the SIP messaging will not arrive at these backgrounded sessions anymore, that is
 * the only way they can terminate.
 */
void stop_handling_sip (int signal) {
	if (signal == SIGHUP) {
		close_sockets ();	// Well, the SIP sockets at least
		signaled_HUP = 1;
	} else {
		;			// Ignore
	}
}


/* Main program.  Parse commandline, setup daemon, start running.
 * TODO: Log output, rather than dumping it on stdout.  Distinguish DEBUG level.
 */
int main (int argc, char *argv []) {
	//
	// Open a logging channel
	openlog ("sipproxy64", LOG_NDELAY | LOG_PID, LOG_DAEMON);
	//
	// Parse the commandline, setup address mapping, open resources
	open_cmdline (argc, argv);
	//
	// Testing mode: Assure that any file descriptors < 10 are dangling
	if (seen_t) {
		int ten;
		do {
			ten = socket (AF_INET6, SOCK_DGRAM, 0);
		} while (ten < 10);
		close (ten);
	}
	//
	// DEBUG
	struct v4v6map *map = v4v6maplist;
	while (map) {
		char v4ip [INET46_ADDRPORTSTRLEN];
		char v6ip [INET46_ADDRPORTSTRLEN];
		*soxcat (v4ip, AF_INET,  (struct sockaddr *) &map->v4addr, 5060) = '\0';
		*soxcat (v6ip, AF_INET6, (struct sockaddr *) &map->v6addr, 5060) = '\0';
		syslog (LOG_INFO, "Created SIP mapping: %s <===> %s\n", v4ip, v6ip);
		map = map->next;
	}
	//
	// Listen to sockets -- at least the local proxy endpoints and possibly phone addresses
	open_sockets ();
	if ((v4param.local_sox == -1) || (v6param.local_sox == -1)) {
		fprintf (stderr, "%s: You should have specified both an IPv4 and IPv6 local listen address\n", program);
		exit (1);
	}
	//
	// Run the proxy as a SIP forwarding application
	pid_t pid = seen_t? 0: fork ();
	switch (pid) {
	case -1:	/* error */
		fprintf (stderr, "%s: Failed to fork daemon: %s\n", program, strerror (errno));
		closelog ();
		exit (1);
	case 0:		/* child */
		if (!seen_t) {
			close (0);
			close (1);
			close (2);
			setsid ();
		}
		signal (SIGHUP, stop_handling_sip);
		run_proxy ();
		close_sockets ();
		break;
	default:	/* parent */
		syslog (LOG_INFO, "Forked to child process %d\n", pid);
		close_sockets ();
		break;
	}
	//
	// Done, the nice way.
	closelog ();
	exit (0);
}
